package cn.learn.validate;

import cn.learn.config.MyAuthenticationFailureHandler;
import cn.learn.properties.MySecurityConstants;
import cn.learn.properties.SecurityProperties;
import io.micrometer.core.instrument.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 增加验证码过滤器
 * 继承OncePerRequestFilter 保证这个过滤器只执行一次
 * 在UsernamePasswordAuthenticationFilter之前拦截验证
 * 这个过滤器执行验证码拦截校验业务
 *
 * @author huangyezhan
 * @date 2020年2月18日22:58:57
 */
@Component
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class);

    /**
     * 失败处理器
     */
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    /**
     * 系统配置信息
     */
    @Autowired
    private SecurityProperties             securityProperties;
    /**
     * 系统中的校验码处理器
     */
    @Autowired
    private ValidateCodeProcessorHolder    validateCodeProcessorHolder;


    /**
     * 存放所有需要校验验证码的url，key为url value为类型的请求参数
     */
    private Map<String, ValidateCodeType> urlMap      = new HashMap<>();
    /**
     * 路径匹配工具
     */
    private AntPathMatcher                pathMatcher = new AntPathMatcher();


    /**
     * afterPropertiesSet方法，这个方法将在所有的属性被初始化后调用；但是会在init前调用，优先于bean配置中有的init-method配置
     *
     * @throws ServletException
     */
    /**
     * 初始化要拦截的url配置信息
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();

        urlMap.put(MySecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);
        addUrlToMap(securityProperties.getCode().getImage().getUrl(), ValidateCodeType.IMAGE);

        urlMap.put(MySecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, ValidateCodeType.SMS);
        addUrlToMap(securityProperties.getCode().getSms().getUrl(), ValidateCodeType.SMS);
    }

    /**
     * 讲系统中配置的需要校验验证码的URL根据校验的类型放入map
     *
     * @param urlString 请求路径
     * @param type      请求参数的参数类型
     */
    protected void addUrlToMap(String urlString, ValidateCodeType type) {
        if (StringUtils.isNotBlank(urlString)) {
            String[] urls = urlString.split(",");
            for (String url : urls) {
                urlMap.put(url, type);
            }
        }
    }


    /**
     * @param request     请求
     * @param response    响应
     * @param filterChain 过滤器
     * @throws ServletException Servlet异常
     * @throws IOException      IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        ValidateCodeType type = this.getValidateCodeType(request);
        if (type != null) {
            logger.info("登陆用户名称：[{}]，即将进行验证码校验；校验码类型为：{}。", request.getParameter("username"), type);
            try {
                validateCodeProcessorHolder.findValidateCodeProcessor(type).validate(request);
                //短信登录不获取登录账号,获取的是手机号
                String requestParameter = null;
                if(type == ValidateCodeType.SMS){
                    requestParameter = request.getParameter(MySecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE);
                }else {
                    requestParameter = request.getParameter("username");
                }
                logger.info("登陆用户名称：[{}]；验证码校验通过。", requestParameter);
            } catch (ValidateCodeException e) {
                myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }


    /**
     * 获取校验码的类型，如果当前请求不需要校验，则返回null
     * 请求为POST的才是需要检验验证码
     *
     * @param request 请求
     * @return 返回请求的参数名称（图形化验证码/短信验证码的参数名称）
     */
    private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
        ValidateCodeType result = null;
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            Set<String> urls = urlMap.keySet();
            for (String url : urls) {
                if (pathMatcher.match(url, request.getRequestURI())) {
                    result = urlMap.get(url);
                }
            }
        }
        return result;
    }

}
